#include "AssetsLoader.h"
#include <SEngine/Render/Entity/MeshFilter.h>
#include <SEngine/Render/Entity/Loader/ModelLoader.h>
#include <SECore/Manager.h>
#include <SERender/Resourses/Loader/ShaderLoader.h>
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <filesystem>
#include <SECore/CoreApplication.h>
#include <memory>
#include <SEngine/Render/RenderFeature/UBORenderFeature.h>
#include <SESerializeYAML/MaterialSerializer.h>
#include <SEngine/Script/ScriptState.h>
#include <map>
#include <SEDebug/Log/Log.h>


namespace SEngine
{

    void LoadEmptyAssets(AssetsElem & elem, const std::string& name) {}
    void LoadScriptAssets(AssetsElem & elem, const std::string& name) {
        Manager::GetInstance().Register<ScriptState>(name, elem.path, std::make_unique<ScriptState>(elem.path));
    }
    void LoadMeshAssets(AssetsElem & elem, const std::string& name) {
        ModelLoader loader;
        loader.Load(elem.path);
        Manager::GetInstance().Register<MeshFilter>(name, elem.path, loader.ExportMeshFilter());
    }
    void LoadMaterialAssets(AssetsElem & elem, const std::string& name) {
        auto mtl = std::make_unique<Material>();
        mtl->Deserialize(SEReadText(elem.path));
        Manager::GetInstance().Register<Material>(name, elem.path, std::move(mtl));
    }
    void LoadShaderAssets(AssetsElem & elem, const std::string& name) {
        ShaderLoader loader;
        Manager::GetInstance().Register<Shader>(name, elem.path, loader.Load(elem.path).CreateShader<Shader>(EngineUniformBlockBinder()));
        Manager::GetInstance().Register<Material>(name, elem.path, std::make_unique<Material>(Manager::GetInstance().Get<Shader>(name).value()));
    }
    void LoadFontAssets(AssetsElem & elem, const std::string& name) {
    }
    void LoadTextureAssets(AssetsElem & elem, const std::string& name) {
        TextureLoader<Texture2D> loader;
        Manager::GetInstance().Register<Texture>(name, elem.path, loader.CreateTexture(elem.path));
    }
    void LoadTextureHDRAssets(AssetsElem & elem, const std::string& name) {
        TextureLoader<TextureHDR> loader;
        Manager::GetInstance().Register<TextureHDR>(name, elem.path, loader.CreateTexture(elem.path));
    }

    AssetsLoader::LoaderTable AssetsLoader::s_assetsLoaderTable = {
        {"FONT",    std::bind(LoadFontAssets, std::placeholders::_1, std::placeholders::_2)},
        {"TEXTURE",    std::bind(LoadTextureAssets, std::placeholders::_1, std::placeholders::_2)},
        {"MATERIAL",  std::bind(LoadMaterialAssets, std::placeholders::_1, std::placeholders::_2)},
        {"SHADER",   std::bind(LoadShaderAssets, std::placeholders::_1, std::placeholders::_2)},
        {"MESH",    std::bind(LoadMeshAssets, std::placeholders::_1, std::placeholders::_2)},
        {"SCRIPT",    std::bind(LoadScriptAssets, std::placeholders::_1, std::placeholders::_2)},
        {"TEXTUREHDR",    std::bind(LoadTextureHDRAssets, std::placeholders::_1, std::placeholders::_2)},
    };
    /**
     * @brief   type: 类型
     *          order: 优先级
     */
    struct AssetsInfo { std::string type; int order; }; 
    std::unordered_map<std::string, AssetsInfo> s_assetsTypeTable = {
        {".ttf",    {"FONT", 1}},
        
        {".png",    {"TEXTURE", 1}},
        {".jpg",    {"TEXTURE", 1}},
        
        {".hdr",    {"TEXTUREHDR", 1}},
        
        {".semat",  {"MATERIAL", 3}},
        
        {".glsl",   {"SHADER", 2}},

        {".fbx",    {"MESH", 1}},
        {".obj",    {"MESH", 1}},
        {".stl",    {"MESH", 1}},

        {".lua",    {"SCRIPT", 1}},
    };

    AssetsLoader::AssetsLoader(const std::string & rootPath)
        : m_rootPath(rootPath)
    {
    }
    
    int AssetsLoader::ScanFileTree(AssetsElem & rootElem, const std::string & rootPath, std::function<void(AssetsElem&, LoaderTable&)> && fn)
    {
        std::multimap<int, LoaderFn> loaderTasks;
        int num = this->ScanFileTreeImpl(rootElem, rootPath, std::move(fn), loaderTasks);
        for (auto && [_, task] : loaderTasks) {
            task();
        }
        return num;
    }

    int AssetsLoader::ScanFileTreeImpl(AssetsElem &curElem, const std::string &curPath, std::function<void(AssetsElem&, LoaderTable&)> && fn, std::multimap<int, LoaderFn> & loaderTasks)
    {
        int num = 0;
        std::filesystem::path path(curPath);
        curElem.path = curPath;
        curElem.name = path.filename().string();
        curElem.rPath = RelativelyPath(m_rootPath, curPath);
        if (std::filesystem::is_directory(path)) {
            curElem.type = AssetsElem::Type::DIR;
            for (auto & entry : std::filesystem::directory_iterator(path)) {
                if (std::filesystem::is_directory(entry.path()) || IsSupportAsset(entry.path().string())) {
                    auto next = std::make_unique<AssetsElem>();
                    num += ScanFileTreeImpl(*next, entry.path().string(), std::forward<decltype(fn)>(fn), loaderTasks);
                    curElem.subElems.emplace_back(std::move(next));
                }
            }
        } else {
            curElem.type = AssetsElem::Type::FILE;
            auto info = s_assetsTypeTable.at(std::filesystem::path(curElem.path).extension().string());
            curElem.assetsTypeName = info.type;
            loaderTasks.emplace(info.order, [fn, &curElem]() { fn(curElem, s_assetsLoaderTable); });
            num += 1;
        }
        return num;
    }

    int AssetsLoader::ForeachFileTree(AssetsElem &curElem, std::function<void(AssetsElem &)> &&fn)
    {
        fn(curElem);
        if (curElem.type == AssetsElem::Type::DIR) {
            for (auto & subElem : curElem.subElems) {
                return ForeachFileTree(*subElem, std::move(fn)) + 1;
            }
        }
        return 1;
    }

    std::string AssetsLoader::RelativelyPath(std::string rootPath, std::string absPath)
    {
        std::reverse(rootPath.begin(), rootPath.end());
        std::reverse(absPath.begin(), absPath.end());
        auto it = std::search(absPath.begin(), absPath.end(), rootPath.begin(), rootPath.end());
        if (it == absPath.end()) {
            std::reverse(absPath.begin(), absPath.end());
            return absPath;
        }
        std::string newPath;
        for (auto iter = absPath.begin(); iter != it; ++iter) {
            newPath += *iter;
        }
        std::reverse(newPath.begin(), newPath.end());
        return newPath;
    }

    bool AssetsLoader::IsSupportAsset(const std::string &path)
    {
        std::filesystem::path path_(path);
        return s_assetsTypeTable.find(path_.extension().string()) != s_assetsTypeTable.end(); 
    }
}